/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.shapes;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.JTextField;

import edu.cmu.ri.createlab.jj.blocks.BlockBlock;
import edu.cmu.ri.createlab.jj.blocks.BlockInterface;
import edu.cmu.ri.createlab.jj.blocks.ExpressionBlock;
import edu.cmu.ri.createlab.jj.blocks.FlowBlock;
import edu.cmu.ri.createlab.jj.blocks.RefBlock;
import edu.cmu.ri.createlab.jj.blocks.VarBlock;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.BlockTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.ExpressTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.FlowTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.VarTypes;
import edu.cmu.ri.createlab.jj.shapes.JJShapeFactory.PathTypes;
import edu.cmu.ri.createlab.jj.shapes.ShapeDefs.IndentTypes;
import edu.cmu.ri.createlab.jj.ui.JJPanelInterface;
import edu.cmu.ri.createlab.jj.ui.JJTextField;
import edu.cmu.ri.createlab.jj.ui.WorkPanel;
import edu.cmu.ri.createlab.jj.visitors.BVDrawer;
import static edu.cmu.ri.createlab.jj.shapes.ShapeDefs.*;

/*
 * BlockShapes contains 3 shapes, a title, a space for vars, and an end. This shape
 * accounts for the top and bottom of an open BlockBlock.
 */
public class BlockShape implements JJShapeInterface
{
   static final int startX = 10;
   static final int startY = 10;
   public enum BlockShapes
   {
      PARAM,
      VAR,
      RETURN
   }
   BlockBlock block;
   BlockPath title;
   BlockPath varsPath;
   BlockPath end;
   JJPanelInterface parent;
   LinkedList<VarShape> params;
   LinkedList<VarShape> vars;
   ArrayList<RefShape> code;
   LinkedList<RefShape> returns;
   LinkedList<IndentTypes> currIndents;
   LinkedList<IndentShape> indents;
   ///This is the lower long horizontal line on the vars shape. It is where
   ///other blocks will be placed to line up and 'snap' onto the vars.
   int varsBot;
   ///This is the upper long horizontal line on the end shape. It is where
   ///other blocks will be placed to line up and 'snap' above the end.
   int endTop;
   ///The scale to draw everything at.
   double scale;
   private boolean drawYellow;
   private int dyY;
   private int dyX;
   private int dyType;
   
   public BlockShape(BlockBlock block, double scale, JJPanelInterface parent)
   {
      this.block = block;
      params = new LinkedList<VarShape>();
      vars = new LinkedList<VarShape>();
      returns = new LinkedList<RefShape>();
      code = new ArrayList<RefShape>();
      indents = new LinkedList<IndentShape>();
      currIndents = new LinkedList<IndentTypes>();
      this.parent = parent;
      varsBot = 0;
      endTop = 0;
      drawYellow = false;
      dyX = 0;
      dyY = 0;
      this.scale = scale;
      if(block != null)
      {
         JJShapeFactory sf = new JJShapeFactory();
         title = sf.buildPath(startX, startY, PathTypes.METHOD_TITLE, block.getParams().size(), scale);
         int vX = (int) (startX + varIndent*scale);
         int vY = (int) (title.getTop() + mTitleTopHeight*scale);
         for(int i = 0; i < block.getParams().size(); i++)
         {
            VarShape tempVar = new VarShape(vX, vY, block.getParams().get(i), scale);
            vars.add(tempVar);
            vY += (int)(middleHeight*scale);
         }
         varsPath = sf.buildPath(startX, title.getBot(), PathTypes.VARS_SPACE, block.getVars().size(), scale);
         vX = (int) (startX + varIndent*scale);
         vY = (int) (title.getBot() + varSpaceTopHeight*scale);
         for(int i = 0; i < block.getVars().size(); i++)
         {
            VarShape tempVar = new VarShape(vX, vY, block.getVar(i), scale);
            vars.add(tempVar);
            if(block.getVar(i).getLength() == 1 && 
                  (block.getVar(i).getVarType() != VarTypes.OBJECT 
                        || block.getVar(i).getObjectType().equals("String")))
            {
               JJTextField tempField = new JJTextField(block.getVar(i).getValue(0), true);
               tempField.setBlock(block.getVar(i));
               tempField.setPreferredSize(new Dimension((int)(valueWidth*scale), (int)(valueHeight*scale)));
               ((Container) parent).add(tempField);
               tempField.setBounds(vX+(int)(varValIndent*scale), vY, (int)(valueWidth*scale), (int)(valueHeight*scale));
            }
            vY += (int)(middleHeight*scale);
         }
         vX = startX;
         vY = varsPath.getBot();
         for(int i = 0; i < block.getCode().size(); i++)
         {
            RefBlock currRef = block.getCode().get(i);
            if(currRef.getBlock().getType() == BlockTypes.FLOW
                  && currRef.getIndex() > 0)
            {
               vX -= (int)indentWidth*scale;
               if(vX < startX) vX = startX;
               if(!currIndents.isEmpty())currIndents.removeLast();
            }
            
            Iterator<IndentTypes> it = currIndents.iterator();
            int indentX = startX;//vX - (int)(indentWidth*scale);
            RefShape tempC = new RefShape(vX, vY, currRef, scale, parent);
            while(it.hasNext())
            {
               indents.add(new IndentShape(indentX, vY, it.next(), scale, tempC.getBot()-tempC.getTop()));
               indentX += (int)indentWidth*scale;
            }
            
            if(currRef.getBlock().getType() == BlockTypes.FLOW
                  && currRef.getIndex() < 3)
            {
               //TODO add code to choose correct indent type to add
               currIndents.add(IndentTypes.DEFAULT);
               vX += (int)indentWidth*scale;
            }
            vY = tempC.getBot();
            code.add(tempC);
         }
         end = sf.buildPath(startX, vY, PathTypes.METHOD_END, returns.size(), scale);
         if(!block.getReturns().isEmpty())
         {
            vX = (int) (startX + varIndent*scale);
            vY = (int) (vY + varSpacing*scale);
            RefShape tempR = new RefShape(vX,vY, block.getReturns().get(0), scale, parent);
            returns.add(tempR);
         }
//         handleParams(block);
//         handleVars(block);
//         handleReturns(block);
      }
      else
      {
         title = null;
         varsPath = null;
         end = null;
      }
      return;
   }
   
   public BlockBlock getBlock()
   {
      return block;
   }
   
   public void setDY(boolean dy, int x, int y, int type)
   {
      drawYellow = dy;
      dyX = x;
      dyY = y;
      dyType = type;
   }
   
   public void addBlocks(LinkedList<RefBlock> blocks, int x, int y)
   {
      int count = 0;
      if(y <= varsPath.getBot())
      {
         for(int j = 0; j < blocks.size(); j++)
         {
            BlockInterface tempBlock = blocks.get(j).getBlock();
            if(tempBlock != null 
                  && tempBlock.getType() != BlockTypes.VAR 
                  && (tempBlock.getType() != BlockTypes.EXPRESSION
                     || ((ExpressionBlock)tempBlock).getEType() == ExpressTypes.ASSIGN))
            {
               block.addCode(blocks.get(j), count++);
            }
         }
//            if(blocks.get(j).getBlock() != null && blocks.get(j).getBlock().getType())
//            block.addCode(blocks.get(j), j);
      }
      else if(y >= end.getTop())
      {
         for(int j = 0; j < blocks.size(); j++)
         {
            BlockInterface tempBlock = blocks.get(j).getBlock();
            if(tempBlock != null 
                  && tempBlock.getType() != BlockTypes.VAR 
                  && (tempBlock.getType() != BlockTypes.EXPRESSION
                     || ((ExpressionBlock)tempBlock).getEType() == ExpressTypes.ASSIGN))
            {
               block.addCode(blocks.get(j));
            }
         }
      }
      else
      {
         for(int i = 0; i < code.size(); i++)
         {
            if(code.get(i).contains(y))
            {
               if(code.get(i).isNearTop(y))
               {
                  for(int j = 0; j < blocks.size(); j++){
                     BlockInterface tempBlock = blocks.get(j).getBlock();
                     if(tempBlock != null 
                           && tempBlock.getType() != BlockTypes.VAR 
                           && (tempBlock.getType() != BlockTypes.EXPRESSION
                              || ((ExpressionBlock)tempBlock).getEType() == ExpressTypes.ASSIGN))
                     {
                        block.addCode(blocks.get(j), i+count++);
                     }
                     else if(tempBlock != null)
                        code.get(i).addBlocks(blocks, x, y);
                  }
                  break;
               }
               else
               {
                  for(int j = 0; j < blocks.size(); j++){
                     BlockInterface tempBlock = blocks.get(j).getBlock();
                     if(tempBlock != null 
                           && tempBlock.getType() != BlockTypes.VAR 
                           && (tempBlock.getType() != BlockTypes.EXPRESSION
                              || ((ExpressionBlock)tempBlock).getEType() == ExpressTypes.ASSIGN))
                     {
                        block.addCode(blocks.get(j), i + 1 + count++);
                     }
                     else if(tempBlock != null)
                        code.get(i).addBlocks(blocks, x, y);
                  }
                  break;
               }
            }
         }
      }
   }
   
   public RefBlock getRef(int x, int y)
   {
      RefBlock ref = null;
      if(title.contains(x, y))
      {
         Iterator<VarShape> it = params.iterator();
         while(it.hasNext())
         {
            VarShape tempShape = it.next();
            if(tempShape.contains(x, y)) ref = new RefBlock(11, tempShape.getBlock());
         }
      }
      else if(varsPath.contains(x, y))
      {
         Iterator<VarShape> it = vars.iterator();
         while(it.hasNext())
         {
            VarShape tempShape = it.next();
            if(tempShape.contains(x, y)) ref = new RefBlock(11, tempShape.getBlock());
         }
      }
      else
      {
         Iterator<RefShape> it = code.iterator();
         while(it.hasNext())
         {
            RefShape tempShape = it.next();
            if(tempShape.contains(x, y))
            {
               ref = tempShape.getRef(x, y);
               if(ref.equals(tempShape.getRef()))
               {
                  it.remove();
                  block.remCode(ref);
               }
            }
         }
      }
      ((WorkPanel)parent).setBlock(block);
      return ref;
   }
   
   public LinkedList<RefBlock> getRefs(int x, int y)
   {
      LinkedList<RefBlock> refs = null;
      RefBlock ref = null;
      boolean remove = false;
      int loc = -1;
      if(title.contains(x, y))
      {
         Iterator<VarShape> it = params.iterator();
         while(it.hasNext())
         {
            VarShape tempShape = it.next();
            if(tempShape.contains(x, y)) 
            {
               ref = new RefBlock(11, tempShape.getBlock());
               break;
            }
         }
      }
      else if(varsPath.contains(x, y))
      {
         Iterator<VarShape> it = vars.iterator();
         while(it.hasNext())
         {
            VarShape tempShape = it.next();
            if(tempShape.contains(x, y))
            {
               ref = new RefBlock(11, tempShape.getBlock());
               break;
            }
         }
      }
      else
      {
         Iterator<RefShape> it = code.iterator();
         while(it.hasNext())
         {
            loc++;
            RefShape tempShape = it.next();
            if(tempShape.contains(x, y))
            {
               ref = tempShape.getRef(x, y);
               if(ref.equals(tempShape.getRef()))
               {
                  remove = true;
                  //it.remove();
                  //block.remCode(ref);
               }
               break;
            }
         }
      }
      if(ref != null)
      {
         if(ref.getBlock() != null && ref.getBlock().getType() == BlockTypes.FLOW)
         {
            int dir;
            int indent = 1;
            int tempLoc = loc;
            refs = new LinkedList<RefBlock>();
            refs.add(ref);
            if(ref.getIndex() == 0) dir = 1;
            else dir = -1;
            int size = block.getCode().size();
            while(indent > 0 && loc >= 0 && loc < size)
            {
               loc += dir;
               if(loc < 0 || loc == size) break;
               RefBlock tempRef = block.getCode().get(loc);
               if(dir == 1) refs.add(tempRef);
               else refs.add(0, tempRef);
               if(tempRef.getBlock() != null && tempRef.getBlock().getType() == BlockTypes.FLOW)
               {
                  if(tempRef.getIndex() == 0)
                     indent += dir;
                  else if(tempRef.getIndex() == 3) indent -= dir;
               }
            }
            if(((FlowBlock)ref.getBlock()).getFType() == FlowTypes.IFELSE && ref.getIndex() == 2)
            {
               indent = 1;
               dir = -dir;
               loc = tempLoc;
               while(indent > 0 && loc >= 0 && loc < size)
               {
                  loc += dir;
                  if(loc < 0 || loc == size) break;
                  RefBlock tempRef = block.getCode().get(loc);
                  if(dir == 1) refs.add(tempRef);
                  else refs.add(0, tempRef);
                  if(tempRef.getBlock() != null && tempRef.getBlock().getType() == BlockTypes.FLOW)
                  {
                     if(tempRef.getIndex() == 0)
                        indent += dir;
                     else indent -= dir;
                  }
               }
            }
         }
         else 
         {
            refs = new LinkedList<RefBlock>();
            refs.add(ref);
         }
         if(remove)
         {
            Iterator<RefBlock> it = refs.iterator();
            while(it.hasNext()) block.remCode(it.next());
         }
      }
      //((WorkPanel)parent).setBlock(block);
      return refs;
   }
   
   public int getVarsBot()
   {
      return varsBot;
   }
   
   public int getEndTop()
   {
      return endTop;
   }
   
   public void setScale(double scale)
   {
      this.scale = scale;
      //TODO add code to redraw at scale
   }
   
   public BlockShapes whichContains(int x, int y)
   {
      if(title == null || varsPath == null || end == null) return null;
      if(title.contains(x, y)) return BlockShapes.PARAM;
      if(varsPath.contains(x, y)) return BlockShapes.VAR;
      if(end.contains(x, y)) return BlockShapes.RETURN;
      return null;
   }
   
   public void addParam(VarShape param)
   {
      params.add(param);
   }
   
   public void addVar(VarShape var)
   {
      vars.add(var);
   }
   
   public void addRet(RefShape ret)
   {
      returns.add(ret);
   }
   
   public VarShape getParam(int x, int y)
   {
      if(whichContains(x, y) == BlockShapes.PARAM)
      {
         Iterator<VarShape> it = params.iterator();
         while(it.hasNext())
         {
            VarShape temp = it.next();
            if(temp.contains(x, y)) return temp;
         }
      }
      return null;
   }
   
   public void remParam(VarShape param)
   {
      params.remove(param);
   }
   
   public void remVar(VarShape var)
   {
      vars.remove(var);
   }
   
   public void remReturn(RefShape ret)
   {
      returns.remove(ret);
   }
   
   public VarShape getVar(int x, int y)
   {
      if(whichContains(x, y) == BlockShapes.VAR)
      {
         Iterator<VarShape> it = vars.iterator();
         while(it.hasNext())
         {
            VarShape temp = it.next();
            if(temp.contains(x, y)) return temp;
         }
      }
      return null;
   }
   
   public RefShape getReturn(int x, int y)
   {
      if(whichContains(x, y) == BlockShapes.RETURN)
      {
         Iterator<RefShape> it = returns.iterator();
         while(it.hasNext())
         {
            RefShape temp = it.next();
            if(temp.contains(x, y)) return temp;
         }
      }
      return null;
   }
   
   
   public void clear()
   {
      title = null;
      varsPath = null;
      end = null;
   }
   
   
   public boolean clicked()
   {
      //TODO check what was clicked and respond accordingly
      return false;
   }
   
   public void clicked(int x, int y)
   {
      Iterator<RefShape> it = code.iterator();
      while(it.hasNext())
      {
         RefShape tempShape = it.next();
         if(tempShape.contains(x, y))
         {
            tempShape.clicked(x, y);
            break;
         }
      }
   }

   
   public boolean contains(int x, int y)
   {
      if(title == null || varsPath == null || end == null) return false;
      if( title.contains(x, y) || varsPath.contains(x, y) || end.contains(x, y))
         return true;
      Iterator<RefShape> it = code.iterator();
      while(it.hasNext())
      {
         if(it.next().contains(x, y)) return true;
      }
      return false;
   }
   
   public boolean contains(int y)
   {
      return y > title.getTop() && y < end.getBot();
   }

   
   public void draw(Graphics g, ImageObserver o, BVDrawer drawer)
   {
      // TODO Auto-generated method stub
//      Graphics2D g2d = (Graphics2D)g;
      drawer.setStart(startX, startY);
      drawer.setGraphics(g);
      drawer.setObserver(o);
      drawer.setScale(scale);
      this.block.accept(drawer);
      drawer.setStart(startX, title.getBot());
      drawer.drawVarSpace(block);
//      g2d.draw(this.varsPath.getPath());
      drawer.setStart(startX, end.getTop());
      drawer.drawEnd(block);
      Iterator<VarShape> vIt = params.iterator();
      while(vIt.hasNext())
         vIt.next().draw(g, o, drawer);
      vIt = vars.iterator();
      while(vIt.hasNext())
         vIt.next().draw(g, o, drawer);
      Iterator<RefShape> rIt = returns.iterator();
      while(rIt.hasNext())
         rIt.next().draw(g, o, drawer);
      rIt = code.iterator();
      while(rIt.hasNext())
         rIt.next().draw(g, o, drawer);
      Iterator<IndentShape> iIt = indents.iterator();
      while(iIt.hasNext())
         iIt.next().draw(g, o, drawer);
      if(drawYellow)
      {
         g.setColor(Color.cyan);
         if(dyY < varsPath.getBot() && dyType == DY_CODE)
         {
            g.drawLine(startX, varsPath.getBot(), startX+300, varsPath.getBot());
            g.drawLine(startX, varsPath.getBot()+1, startX+300, varsPath.getBot()+1);
         }
         else if(dyY > end.getTop() && dyType == DY_CODE)
         {
            g.drawLine(startX, varsPath.getBot(), startX+300, varsPath.getBot());
            g.drawLine(startX, varsPath.getBot()+1, startX+300, varsPath.getBot()+1);
         }
         else
         {
            Iterator<RefShape> cIt = code.iterator();
            while(cIt.hasNext())
            {
               RefShape tempRS = cIt.next();
               if(tempRS.contains(dyY))
               {
                  if(dyType == DY_CODE)
                  {
                     if(tempRS.isNearTop(dyY))
                     {
                        g.drawLine(startX, tempRS.getTop(), startX+300, tempRS.getTop());
                        g.drawLine(startX, tempRS.getTop()+1, startX+300, tempRS.getTop()+1);
                     }
                     else
                     {
                        g.drawLine(startX, tempRS.getBot(), startX+300, tempRS.getBot());
                        g.drawLine(startX, tempRS.getBot()+1, startX+300, tempRS.getBot()+1);
                     }
                  }
                  else if(dyType == DY_VAR)
                  {
                     tempRS.drawYellow(dyX, dyY, g);
                  }
                  break;
               }
            }
         }
      }
   }

   
   public int[] getBounds()
   {
      // TODO Auto-generated method stub
      int[] bounds = new int[4];
      bounds[0] = startX;
      bounds[1] = startY;
      bounds[2] = (int)end.getPath().getBounds().getMaxX();
      bounds[3] = (int)end.getPath().getBounds().getMaxY();
      return bounds;
   }

   
   public void setOrigin(int x, int y)
   {
      // TODO Auto-generated method stub

   }

}
